/**
 * @file ebex_cfg.c
 *
 * @date Sep 19, 2011
 * @author seth
 *
 * @brief This file is part of EBEX State Estimator, created for the EBEX project
 *
 * This software is copyright (C) 2011 Columbia University
 *
 * EBEX State Estimator is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * EBEX State Estimator is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EBEX State Estimator; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <inttypes.h>

#include <ctype.h>
#include <netdb.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <envz.h>

#include <memory_util.h>
#include <ebex_cfg.h>

ebex_cfg_varhandler_t *varhandlers = NULL;
static ebex_cfg_err_t ebex_cfg_errno = EBEX_CFG_ERR_NONE;
const char *ebex_cfg_usererrmsg = NULL;
static const char *ebex_cfg_errfile = NULL;
int ebex_cfg_optind = 0;
static int ebex_cfg_errline = 0;

extern char **environ;

static int ebex_cfg_process_file(const char *m_cfgfile, const char *m_prefix);

static int ebex_cfg_process_include(const char *m_pathname, const char *m_prefix)
{
	struct stat pathinfo;
	struct dirent *dinfo = NULL;
	char includepath[EBEX_CFG_LINEBUF_LEN] =
		{ 0 };
	DIR *dh = NULL;
	int statret = -1, lcpcafret = -1;
	int retval = 0;

	statret = stat(m_pathname, &pathinfo);
	if (statret < 0)
	{
		return (-1);
	}

	if (S_ISDIR(pathinfo.st_mode))
	{
		dh = opendir(m_pathname);
		if (dh == NULL)
		{
			return (-1);
		}

		while (1)
		{
			dinfo = readdir(dh);
			if (dinfo == NULL)
			{
				break;
			}

			/* Skip files that begin with a dot ('.') */
			if (dinfo->d_name[0] == '.')
				continue;

			snprintf(includepath, sizeof(includepath) - 1, "%s/%s", m_pathname, dinfo->d_name);
			lcpcafret = ebex_cfg_process_include(includepath, m_prefix);
			if (lcpcafret < 0)
			{
				retval = -1;
				break;
			}
		}

		closedir(dh);
	}
	else
	{
		lcpcafret = ebex_cfg_process_file(m_pathname, m_prefix);
		if (lcpcafret < 0)
		{
			retval = -1;
		}
	}

	return (retval);
}

static inline void ebex_cfg_string_unescape(char *m_dest, char *m_src) {
    int i, j;
    i = j = 0;

    while ( m_src[i] ) {
        switch ( m_src[i] ) {
        case '\\':
            switch( m_src[++i] ) {
            case 'n':
                m_dest[j] = '\n';
                break;
            case 't':
                m_dest[j] = '\t';
                break;
            case 'a':
                m_dest[j] = '\a';
                break;
            case 'b':
                m_dest[j] = '\b';
                break;
            case 'f':
                m_dest[j] = '\f';
                break;
            case 'r':
                m_dest[j] = '\r';
                break;
            case 'v':
                m_dest[j] = '\v';
                break;
            case '\\':
                m_dest[j] = '\\';
                break;
            case '\"':
                m_dest[j] = '\"';
                break;
            default:
                m_dest[j++] = '\\';
                m_dest[j] = m_src[i];
                break;
            }
            break;

        default:
            m_dest[j] = m_src[i];
            break;
        }
        ++i;
        ++j;
    }
    m_dest[j] = m_src[i];    /*  Don't forget the null character  */
}

int ebex_cfg_var_type_string_callback(void *m_data, const char *m_val, const char **m_endp)
{
	char **dataval;
	size_t len;

	dataval = m_data;

	if (*m_val == '\"')
	{
		len = strcspn(++m_val, "\"");
	}

	*dataval = e_strndup(m_val, len + 1);
	ebex_cfg_string_unescape(*dataval, *dataval);

	*m_endp = NULL;

	return (0);
}

int ebex_cfg_var_type_hostname_callback(void *data, const char *m_val, const char **m_endp)
{
	struct hostent *hostname;

	hostname = gethostbyname(m_val);
	if (hostname == NULL)
	{
		return (-1);
	}

	if (hostname->h_length != 4)
	{
		return (-1);
	}

	if (hostname->h_addr_list[0] == 0)
	{
		return (-1);
	}

	memcpy(data, hostname->h_addr_list[0], sizeof(uint32_t));

	return (0);
}

int ebex_cfg_var_type_ip_callback(void *data, const char *m_val, const char **m_endp)
{
	uint32_t ipval = 0, curr_ipval = 0;
	const char *valptr;
	int retval = 0;
	int dotcount = 0;

	for (valptr = m_val; *valptr; valptr++)
	{
		if (!isdigit(*valptr))
		{
			if (*valptr == '.' || *valptr == ',')
			{
				dotcount++;
				if (dotcount >= 4)
				{
					retval = -1;
					break;
				}

				if (curr_ipval > 255)
				{
					retval = -1;
					break;
				}

				/* For lists */
				if (*valptr == ',')
				{
					break;
				}

				ipval |= curr_ipval << ((dotcount - 1) * 8);
				curr_ipval = 0;

				continue;
			}
			else
			{
				retval = -1;
				break;
			}
		}

		curr_ipval *= 10;
		curr_ipval += *valptr - '0';
	}

	if (curr_ipval > 255)
	{
		retval = -1;
	}

	if (retval == 0)
	{
		ipval |= curr_ipval << 24;
		*(uint32_t*)data = ipval;
	}

	return (retval);
}

int ebex_cfg_var_type_section_callback(void *m_data, const char *m_val, const char **m_endp)
{
	die("Invalid use");
	return -1;
}

int ebex_cfg_var_type_sectionstart_callback(void *m_data, const char *m_val, const char **m_endp)
{
	die("Invalid use");
	return -1;
}

int ebex_cfg_var_type_sectionend_callback(void *m_data, const char *m_val, const char **m_endp)
{
	die("Invalid use");
	return -1;
}

int ebex_cfg_var_type_unknown_callback(void *m_data, const char *m_val, const char **m_endp)
{
	die("Invalid use");
	return -1;
}

int ebex_cfg_var_type_none_callback(void *m_data, const char *m_val, const char **m_endp)
{
	die("Invalid use");
	return -1;
}

int ebex_cfg_var_type_addr_callback(void *m_data, const char *m_val, const char **m_endp)
{
	if (ebex_cfg_var_type_ip_callback(m_data, m_val, m_endp) &&  ebex_cfg_var_type_hostname_callback(m_data, m_val, m_endp))
	{
		return -1;
	}

	return 0;
}

int ebex_cfg_var_type_long_long_callback(void *m_data, const char *m_val, const char **m_endp)
{
	*(long long *)m_data = strtoimax(m_val, (char **) m_endp, 10);

	return (0);
}

int ebex_cfg_var_type_long_callback(void *m_data, const char *m_val, const char **m_endp)
{
	*(long*)m_data = strtoimax(m_val, (char **) m_endp, 10);

	return (0);
}

int ebex_cfg_var_type_int_callback(void *m_data, const char *m_val, const char **m_endp)
{
	*(int*)m_data = strtoimax(m_val, (char **) m_endp, 10);

	return (0);
}

int ebex_cfg_var_type_sizet_callback(void *m_data, const char *m_val, const char **m_endp)
{
	*(size_t*)m_data = strtoimax(m_val, (char **) m_endp, 10);

	return (0);
}

int ebex_cfg_var_type_short_callback(void *m_data, const char *m_val, const char **m_endp)
{
	*(short*)m_data = strtoimax(m_val, (char **) m_endp, 10);

	return (0);
}

int ebex_cfg_var_type_bool_by_exist_callback(void *m_data, const char *m_val, const char **m_endp)
{
	*(int*)m_data = 1;

	*m_endp = NULL;

	return (0);
}

int ebex_cfg_var_type_boolean_callback(void *m_data, const char *m_val, const char **m_endp)
{
	char *trueval[] =
		{ "enable", "true", "yes", "on", "y", "1" };
	char *falseval[] =
		{ "disable", "false", "no", "off", "n", "0" };
	size_t chkvallen, vallen;
	int i;
	int *data = (int*) m_data;

	*data = -1;

	vallen = strlen(m_val);

	for (i = 0; i < (int)(sizeof(trueval) / sizeof(*trueval)); i++)
	{
		chkvallen = strlen(trueval[i]);

		/*
		 * Skip if there's no way we could find a match here.
		 */
		if (chkvallen > vallen)
		{
			continue;
		}

		/*
		 * Skip if there is no partial match.
		 */
		if (strncasecmp(m_val, trueval[i], chkvallen) != 0)
		{
			continue;
		}

		if (m_val[chkvallen] == '\0' || m_val[chkvallen] == ',' || m_val[chkvallen] == ' ')
		{
			/* Declare a winner and set the next token. */
			*m_endp = m_val + chkvallen;
			*data = 1;
			return (0);
		}
	}

	for (i = 0; i < (int)(sizeof(falseval) / sizeof(*falseval)); i++)
	{
		chkvallen = strlen(falseval[i]);

		/*
		 * Skip if there's no way we could find a match here.
		 */
		if (chkvallen > vallen)
		{
			continue;
		}

		/*
		 * Skip if there is no partial match.
		 */
		if (strncasecmp(m_val, falseval[i], chkvallen) != 0)
		{
			continue;
		}

		if (m_val[chkvallen] == '\0' || m_val[chkvallen] == ',' || m_val[chkvallen] == ' ')
		{
			/* Declare a winner and set the next token. */
			*m_endp = m_val + chkvallen;
			*data = 0;
			return (0);
		}
	}

	ebex_cfg_errno = EBEX_CFG_ERR_BADFORMAT;
	return (-1);
}

static intmax_t ebex_cfg_process_size(const char *m_val)
{
	intmax_t retval = 0;
	char *mult = NULL;

	retval = strtoimax(m_val, &mult, 10);

	if (mult)
	{
		while (isblank(mult[0]) || mult[0] == '\0')
			mult++;

		switch (tolower(mult[0]))
		{
			case 'p':
				retval *= INTMAX_C(1125899906842624);
				break;
			case 't':
				retval *= INTMAX_C(1958505086976);
				break;
			case 'g':
				retval *= INTMAX_C(1073741824);
				break;
			case 'm':
				retval *= INTMAX_C(1048576);
				break;
			case 'k':
				retval *= INTMAX_C(1024);
				break;
			default:
				break;
		}
	}

	return (retval);
}

int ebex_cfg_var_type_size_long_long_callback(void *m_data, const char *m_val, const char **m_endp)
{
	*(long long *)m_data = ebex_cfg_process_size(m_val);

	return (0);
}

int ebex_cfg_var_type_size_long_callback(void *m_data, const char *m_val, const char **m_endp)
{
	*(long*)m_data = ebex_cfg_process_size(m_val);

	return (0);
}

int ebex_cfg_var_type_size_int_callback(void *m_data, const char *m_val, const char **m_endp)
{
	*(int*)m_data = ebex_cfg_process_size(m_val);

	return (0);
}

int ebex_cfg_var_type_size_short_callback(void *m_data, const char *m_val, const char **m_endp)
{
	*(short*)m_data = ebex_cfg_process_size(m_val);

	return (0);
}

int ebex_cfg_var_type_float_callback(void *m_data, const char *m_val, const char **m_endp)
{
	*(float*)m_data = strtof(m_val, (char **) m_endp);
	return (0);
}

int ebex_cfg_var_type_double_callback(void *m_data, const char *m_val, const char **m_endp)
{
	*(double*)m_data = strtod(m_val, (char **) m_endp);

	return (0);
}

/**
 * Translates a text string to the appropriate value
 * @param m_type Type of variable to process
 * @param m_val Pointer to the NULL-terminated string containing the configuration value
 * @param m_data Pointer to the memory location to receive the processed output
 * @return -1 on failure, 0 on success
 */
static int ebex_cfg_process_type(e_ebex_cfg_var_type m_type, const char *m_val, void *m_data)
{
	const char *next;

	if (m_type >= ebex_cfg_var_type_tblEnd || m_type < 0)
		return -1;

	return ebex_cfg_var_type_lookup_table[m_type].process_var(m_data, m_val, &next);

}

static int ebex_cfg_handle(ebex_cfg_varhandler_t *m_handler, const char *m_var, const char *m_arguments, const char *m_val,
		ebex_cfg_flags_t m_flags)
{
	const char *localvar = NULL;
	int retval;

	if (m_var != NULL)
	{
		localvar = strrchr(m_var, '.');
		if (localvar == NULL)
		{
			localvar = m_var;
		}
		else
		{
			localvar++;
		}
	}
	else
	{
		localvar = NULL;
	}

	switch (m_handler->mode)
	{
		case EBEX_CFG_MODE_CALLBACK:
			if (m_handler->callback != NULL)
			{
				retval = m_handler->callback(localvar, m_var, m_arguments, m_val, m_flags, m_handler->extra);
				if (retval < 0)
				{
					ebex_cfg_errno = EBEX_CFG_ERR_CALLBACK;
				}

				return (retval);
			}
			break;
		case EBEX_CFG_MODE_VAR:
			return (ebex_cfg_process_type(m_handler->type, m_val, m_handler->data));
			break;
	}

	return (-1);
}

static int ebex_cfg_process_environment(const char *m_appname)
{
	ebex_cfg_varhandler_t *handler = NULL;
	size_t appnamelen = 0;
	char varnamebuf[128] =
		{ 0 };
	char **currvar;
	char *sep = NULL, *m_val = NULL, *cmd = NULL;
	char *ucase_appname = NULL;
	char *ucase_appname_itr = NULL;
	char *lastcomponent_handler = NULL;
	int varnamelen = 0;
	char *local_ebex_cfg_errfile;
	int local_ebex_cfg_errline;

	/* Make sure we have an environment to screw with, if not,
	 no arguments were found to be in error */
	if (environ == NULL || m_appname == NULL)
	{
		return (0);
	}

	local_ebex_cfg_errfile = "<environment>";
	local_ebex_cfg_errline = 0;

	/* Allocate and create our uppercase appname. */
	ucase_appname = e_strdup(m_appname);
	if (ucase_appname == NULL)
	{
		ebex_cfg_errfile = local_ebex_cfg_errfile;
		ebex_cfg_errline = local_ebex_cfg_errline;
		ebex_cfg_errno = EBEX_CFG_ERR_ENOMEM;
		return (-1);
	}
	for (ucase_appname_itr = ucase_appname; *ucase_appname_itr != '\0'; ucase_appname_itr++)
	{
		*ucase_appname_itr = (char)toupper(*ucase_appname_itr);
	}

	appnamelen = strlen(ucase_appname);

	for (currvar = environ; *currvar != NULL; currvar++)
	{
		/* If it doesn't begin with our appname ignore it completely. */
		if (strncmp(*currvar, ucase_appname, appnamelen) != 0)
		{
			continue;
		}

		/* Find our seperator. */
		sep = strchr(*currvar, '=');
		if (sep == NULL)
		{
			continue;
		}

		varnamelen = sep - *currvar;

		/* Skip variables that would overflow our buffer. */
		if (varnamelen >= (int)sizeof(varnamebuf))
		{
			continue;
		}

		strncpy(varnamebuf, *currvar, varnamelen);

		varnamebuf[varnamelen] = '\0';
		m_val = sep + 1;

		/* We ignore APPNAME by itself. */
		if (strlen(varnamebuf) <= appnamelen)
		{
			continue;
		}

		/* Further it must be <APPNAME>_ */
		if (varnamebuf[appnamelen] != '_')
		{
			continue;
		}

		cmd = varnamebuf + appnamelen + 1;

		/* We don't allow section specifiers, for reasons see notes in
		 the cmdline processor (below). */
		if (strchr(cmd, '.') != NULL)
		{
			continue;
		}

		for (handler = varhandlers; handler != NULL; handler = handler->_next)
		{
			if (handler->var == NULL)
			{
				continue;
			}

			/* Skip handlers which don't agree with being
			 processed outside a config file */
			if (handler->type == ebex_cfg_var_type_section || handler->type == ebex_cfg_var_type_sectionstart
					|| handler->type == ebex_cfg_var_type_sectionend || handler->type == ebex_cfg_var_type_unknown)
			{
				continue;
			}

			/* Find the last part of the variable and compare it with
			 the option being processed, if a wildcard is given. */
			if (handler->var[0] == '*' && handler->var[1] == '.')
			{
				lastcomponent_handler = strrchr(handler->var, '.');
				if (lastcomponent_handler == NULL)
				{
					lastcomponent_handler = handler->var;
				}
				else
				{
					lastcomponent_handler++;
				}
			}
			else
			{
				lastcomponent_handler = handler->var;
			}

			/* Ignore this handler if they don't match. */
			if (strcasecmp(lastcomponent_handler, cmd) != 0)
			{
				continue;
			}

			if (handler->type == ebex_cfg_var_type_none || handler->type == ebex_cfg_var_type_bool_by_exist)
			{
				m_val = NULL;
			}

			/* We ignore errors from the environment variables,
			 they're mostly insignificant. */
			ebex_cfg_handle(handler, cmd, NULL, m_val, EBEX_CFG_FLAGS_ENVIRON);

			break;
		}
	}

	e_free(ucase_appname);

	return (0);
}

static int ebex_cfg_process_cmdline(int argc, char **argv)
{
	ebex_cfg_varhandler_t *handler = NULL;
	char *cmdarg = NULL, *cmdoptarg = NULL;
	char *lastcomponent_handler = NULL;
	char **newargv = NULL;
	char *usedargv = NULL;
	int cmdargidx = 0;
	int newargvidx = 0;
	int retval = 0, chkretval = 0;
	int ch = 0;
	char *local_ebex_cfg_errfile;
	int local_ebex_cfg_errline;

	local_ebex_cfg_errfile = "<cmdline>";
	local_ebex_cfg_errline = 0;

	/* Allocate "argc + 1" (+1 for the NULL terminator) elements. */
	newargv = e_malloc((argc + 1) * sizeof(*newargv));
	if (newargv == NULL)
	{
		ebex_cfg_errfile = local_ebex_cfg_errfile;
		ebex_cfg_errline = local_ebex_cfg_errline;
		ebex_cfg_errno = EBEX_CFG_ERR_ENOMEM;
		return (-1);
	}
	newargv[newargvidx++] = argv[0];
	newargv[argc] = NULL;

	/* Allocate space to indicate which arguments have been used. */
	usedargv = e_malloc(argc * sizeof(*usedargv));
	if (usedargv == NULL)
	{
		ebex_cfg_errfile = local_ebex_cfg_errfile;
		ebex_cfg_errline = local_ebex_cfg_errline;
		ebex_cfg_errno = EBEX_CFG_ERR_ENOMEM;
		e_free(newargv);
		return (-1);
	}
	for (cmdargidx = 0; cmdargidx < argc; cmdargidx++)
	{
		usedargv[cmdargidx] = 0;
	}

	for (cmdargidx = 1; cmdargidx < argc; cmdargidx++)
	{
		cmdarg = argv[cmdargidx];

		/* Make sure we have an argument here. */
		if (cmdarg == NULL)
		{
			break;
		}

		/* If the argument isn't an option, skip. */
		if (cmdarg[0] != '-')
		{
			continue;
		}

		/* Setup a pointer in the new array for the actual argument. */
		newargv[newargvidx++] = cmdarg;
		usedargv[cmdargidx] = 1;

		/* Then shift the argument past the '-' so we can ignore it. */
		cmdarg++;

		/* Handle long options. */
		if (cmdarg[0] == '-')
		{
			cmdarg++;

			/* Don't process arguments after the '--' option. */
			if (cmdarg[0] == '\0')
			{
				break;
			}

			/* Look for a variable name that matches */
			for (handler = varhandlers; handler != NULL; handler = handler->_next)
			{
				/* Skip handlers with no variable name. */
				if (handler->var == NULL)
				{
					continue;
				}
				/* Skip handlers which don't agree with being
				 processed on the command line. */
				if (handler->type == ebex_cfg_var_type_section || handler->type == ebex_cfg_var_type_sectionstart
						|| handler->type == ebex_cfg_var_type_sectionend || handler->type == ebex_cfg_var_type_unknown)
				{
					continue;
				}

				/* Find the last part of the variable and compare it with
				 the option being processed, if a wildcard is given. */
				if (handler->var[0] == '*' && handler->var[1] == '.')
				{
					lastcomponent_handler = strrchr(handler->var, '.');
					if (lastcomponent_handler == NULL)
					{
						lastcomponent_handler = handler->var;
					}
					else
					{
						lastcomponent_handler++;
					}
				}
				else
				{
					/* Disallow use of the fully qualified name
					 since there was no sectionstart portion
					 we cannot allow it to handle children of it. */
					if (strchr(cmdarg, '.') != NULL)
					{
						continue;
					}
					lastcomponent_handler = handler->var;
				}

				/* Ignore this handler if they don't match. */
				if (strcasecmp(lastcomponent_handler, cmdarg) != 0)
				{
					continue;
				}

				if (handler->type == ebex_cfg_var_type_none || handler->type == ebex_cfg_var_type_bool_by_exist)
				{
					cmdoptarg = NULL;
				}
				else
				{
					cmdargidx++;
					if (cmdargidx >= argc)
					{
						fprintf(stderr, "Argument required.\n");
						ebex_cfg_errfile = local_ebex_cfg_errfile;
						ebex_cfg_errline = local_ebex_cfg_errline;
						ebex_cfg_errno = EBEX_CFG_ERR_BADFORMAT;
						e_free(usedargv);
						e_free(newargv);
						return (-1);
					}
					cmdoptarg = argv[cmdargidx];
					newargv[newargvidx++] = cmdoptarg;
					usedargv[cmdargidx] = 1;
				}

				chkretval = ebex_cfg_handle(handler, handler->var, NULL, cmdoptarg, EBEX_CFG_FLAGS_CMDLINE);
				if (chkretval < 0)
				{
					retval = -1;
				}

				break;
			}

			if (handler == NULL)
			{
				fprintf(stderr, "Unknown option: --%s\n", cmdarg);
				ebex_cfg_errfile = local_ebex_cfg_errfile;
				ebex_cfg_errline = local_ebex_cfg_errline;
				ebex_cfg_errno = EBEX_CFG_ERR_INVCMD;
				e_free(usedargv);
				e_free(newargv);
				return (-1);
			}
		}
		else
		{
			for (; *cmdarg != '\0'; cmdarg++)
			{
				ch = *cmdarg;

				for (handler = varhandlers; handler != NULL; handler = handler->_next)
				{
					if (handler->opt != ch || handler->opt == '\0')
					{
						continue;
					}
					/* Skip handlers which don't agree with being
					 processed on the command line. */
					if (handler->type == ebex_cfg_var_type_section || handler->type == ebex_cfg_var_type_sectionstart
							|| handler->type == ebex_cfg_var_type_sectionend || handler->type == ebex_cfg_var_type_unknown)
					{
						continue;
					}

					if (handler->type == ebex_cfg_var_type_none || handler->type == ebex_cfg_var_type_bool_by_exist)
					{
						cmdoptarg = NULL;
					}
					else
					{
						cmdargidx++;
						if (cmdargidx >= argc)
						{
							fprintf(stderr, "Argument required.\n");
							ebex_cfg_errfile = local_ebex_cfg_errfile;
							ebex_cfg_errline = local_ebex_cfg_errline;
							ebex_cfg_errno = EBEX_CFG_ERR_BADFORMAT;
							e_free(usedargv);
							e_free(newargv);
							return (-1);
						}
						cmdoptarg = argv[cmdargidx];
						newargv[newargvidx++] = cmdoptarg;
						usedargv[cmdargidx] = 1;
					}

					chkretval = ebex_cfg_handle(handler, handler->var, NULL, cmdoptarg, EBEX_CFG_FLAGS_CMDLINE);
					if (chkretval < 0)
					{
						ebex_cfg_errfile = local_ebex_cfg_errfile;
						ebex_cfg_errline = local_ebex_cfg_errline;
						retval = -1;
					}

					break;
				}

				if (handler == NULL)
				{
					fprintf(stderr, "Unknown option: -%c\n", ch);
					ebex_cfg_errfile = local_ebex_cfg_errfile;
					ebex_cfg_errline = local_ebex_cfg_errline;
					ebex_cfg_errno = EBEX_CFG_ERR_INVCMD;
					e_free(usedargv);
					e_free(newargv);
					return (-1);
				}
			}
		}
	}

	if (retval >= 0)
	{
		ebex_cfg_optind = newargvidx;
		for (cmdargidx = 1; cmdargidx < argc; cmdargidx++)
		{
			if (usedargv[cmdargidx] != 0)
			{
				continue;
			}

			cmdarg = argv[cmdargidx];

			newargv[newargvidx++] = cmdarg;
		}
		for (cmdargidx = 1; cmdargidx < argc; cmdargidx++)
		{
			argv[cmdargidx] = newargv[cmdargidx];
		}
	}

	e_free(usedargv);
	e_free(newargv);

	return (retval);
}

int ebex_cfg_process_var(const char *m_var, const char *m_arguments, const char *m_val, ebex_cfg_flags_t flags)
{
	ebex_cfg_varhandler_t *handler = NULL;
	const char *lastcomponent_handler = NULL, *lastcomponent_var = NULL;

	lastcomponent_var = strrchr(m_var, '.');
	if (lastcomponent_var == NULL)
	{
		lastcomponent_var = m_var;
	}
	else
	{
		lastcomponent_var++;
	}

	for (handler = varhandlers; handler != NULL; handler = handler->_next)
	{
		/* If either handler->var or var is NULL, skip, unless both are NULL. */
		if (handler->var != m_var && (handler->var == NULL || m_var == NULL))
		{
			continue;
		}

		/* If both are not-NULL, compare them. */
		if (handler->var != NULL)
		{
			/* Wild-card-ish match. */
			if (handler->var[0] == '*' && handler->var[1] == '.')
			{
				/* Only compare the last components */

				lastcomponent_handler = strrchr(handler->var, '.') + 1; /* strrchr() won't return NULL, because we already checked it. */

				if (strcasecmp(lastcomponent_handler, lastcomponent_var) != 0)
				{
					continue;
				}
			}
			else if (strcasecmp(handler->var, m_var) != 0)
			{
				/* Exact (case-insensitive comparison) failed. */
				continue;
			}
		}

		if (m_val == NULL && handler->type != ebex_cfg_var_type_none && handler->type != ebex_cfg_var_type_bool_by_exist
				&& handler->type != ebex_cfg_var_type_section && handler->type != ebex_cfg_var_type_sectionstart
				&& handler->type != ebex_cfg_var_type_sectionend)
		{
			ebex_cfg_errno = EBEX_CFG_ERR_BADFORMAT;
			break;
		}

		return (ebex_cfg_handle(handler, m_var, m_arguments, m_val, flags));
	}

	return (-1);
}

int ebex_cfg_register_handler(const char *m_var, char m_opt, int m_type, ebex_cfg_callback_t m_callback, void *m_extra)
{
	ebex_cfg_varhandler_t *newhandler = NULL;

	newhandler = e_malloc(sizeof(*newhandler));

	if (newhandler == NULL)
	{
		return (-1);
	}

	if (m_var == NULL)
	{
		newhandler->var = NULL;
	}
	else
	{
		newhandler->var = e_strdup(m_var);
	}
	newhandler->mode = EBEX_CFG_MODE_CALLBACK;
	newhandler->type = (e_ebex_cfg_var_type)m_type;
	newhandler->callback = m_callback;
	newhandler->opt = m_opt;
	newhandler->extra = m_extra;
	newhandler->_next = varhandlers;

	varhandlers = newhandler;

	return (0);
}

int ebex_cfg_register_var(const char *m_var, int m_type, void *m_data, char m_opt)
{
	ebex_cfg_varhandler_t *newhandler = NULL;

	if (!m_data)
	{
		dief("Missing pointer to variable set for '%s'", m_var);
		return EBEX_FAIL;
	}
	newhandler = e_malloc(sizeof(ebex_cfg_varhandler_t));

	if (newhandler == NULL)
	{
		return (-1);
	}

	if (m_var == NULL)
	{
		newhandler->var = NULL;
	}
	else
	{
		newhandler->var = e_strdup(m_var);
	}
	newhandler->mode = EBEX_CFG_MODE_VAR;
	newhandler->type = (e_ebex_cfg_var_type)m_type;
	newhandler->data = m_data;
	newhandler->opt = m_opt;
	newhandler->extra = NULL;
	newhandler->_next = varhandlers;

	varhandlers = newhandler;

	return (0);
}

static int ebex_cfg_process_files(const char *appname, const char *extraconfig)
{

	char configfiles[3][13][512] =
		{
			{
				{ 0 } } };
	char *configfile = NULL;
	char *homedir = NULL;
	int configsetidx = 0, configidx = 0;
	int chkretval = 0, retval = -1;

	if (extraconfig != NULL)
	{
		snprintf(configfiles[0][0], sizeof(**configfiles) - 1, "%s", extraconfig);
	}
	snprintf(configfiles[1][0], sizeof(**configfiles) - 1, "/etc/%s.cfg", appname);
	snprintf(configfiles[1][1], sizeof(**configfiles) - 1, "/etc/%s.conf", appname);
	snprintf(configfiles[1][2], sizeof(**configfiles) - 1, "/etc/%s/%s.cfg", appname, appname);
	snprintf(configfiles[1][3], sizeof(**configfiles) - 1, "/etc/%s/%s.conf", appname, appname);
	snprintf(configfiles[1][4], sizeof(**configfiles) - 1, "/usr/etc/%s.cfg", appname);
	snprintf(configfiles[1][5], sizeof(**configfiles) - 1, "/usr/etc/%s.conf", appname);
	snprintf(configfiles[1][6], sizeof(**configfiles) - 1, "/usr/etc/%s/%s.cfg", appname, appname);
	snprintf(configfiles[1][7], sizeof(**configfiles) - 1, "/usr/etc/%s/%s.conf", appname, appname);
	snprintf(configfiles[1][8], sizeof(**configfiles) - 1, "/usr/local/etc/%s.cfg", appname);
	snprintf(configfiles[1][9], sizeof(**configfiles) - 1, "/usr/local/etc/%s.conf", appname);
	snprintf(configfiles[1][10], sizeof(**configfiles) - 1, "/usr/local/etc/%s/%s.cfg", appname, appname);
	snprintf(configfiles[1][11], sizeof(**configfiles) - 1, "/usr/local/etc/%s/%s.conf", appname, appname);
	if (getuid() != 0)
	{
		homedir = getenv("HOME");
		if (homedir != NULL)
		{
			if (strcmp(homedir, "/") != 0 && access(homedir, R_OK | W_OK | X_OK) == 0)
			{
				snprintf(configfiles[2][0], sizeof(**configfiles) - 1, "%s/.%src", homedir, appname);
				snprintf(configfiles[2][1], sizeof(**configfiles) - 1, "%s/.%s.cfg", homedir, appname);
				snprintf(configfiles[2][2], sizeof(**configfiles) - 1, "%s/.%s.conf", homedir, appname);
				snprintf(configfiles[2][3], sizeof(**configfiles) - 1, "%s/.%s/%s.cfg", homedir, appname, appname);
				snprintf(configfiles[2][4], sizeof(**configfiles) - 1, "%s/.%s/%s.conf", homedir, appname, appname);
				snprintf(configfiles[2][5], sizeof(**configfiles) - 1, "%s/.%s/config", homedir, appname);
			}
		}
	}

	for (configsetidx = 0; configsetidx < 3; configsetidx++)
	{
		for (configidx = 0; configidx < 13; configidx++)
		{
			configfile = configfiles[configsetidx][configidx];
			if (configfile[0] == '\0')
			{
				break;
			}
			if (access(configfile, R_OK) == 0)
			{
				chkretval = ebex_cfg_process_file(configfile, NULL);
				if (chkretval < 0)
				{
					return EBEX_FAIL;
				}

				return EBEX_SUCCESS;

			}
		}
	}

	return (retval);
}

void ebex_cfg_cleanup(void)
{
	ebex_cfg_varhandler_t *handler = NULL;
	ebex_cfg_varhandler_t *next = NULL;

	handler = varhandlers;
	while (handler != NULL)
	{
		if (handler->var != NULL)
		{
			e_free(handler->var);
		}

		next = handler->_next;

		e_free(handler);

		handler = next;
	}

	if (ebex_cfg_usererrmsg)
	{
		e_free((char *) ebex_cfg_usererrmsg);

		ebex_cfg_usererrmsg = NULL;
	}

	varhandlers = NULL;

	return;
}

int ebex_cfg_process(int argc, char **argv, const char *appname, const char *extra)
{
	int retval = 0, chkretval = 0;

	/* Handle config files. */
	chkretval = ebex_cfg_process_files(appname, extra);
	if (chkretval < 0)
	{
		retval = -1;
	}

	/* Handle environment variables.*/
	chkretval = ebex_cfg_process_environment(appname);
	if (chkretval < 0)
	{
		retval = -1;
	}

	/* Handle command line arguments */
	chkretval = ebex_cfg_process_cmdline(argc, argv);
	if (chkretval < 0)
	{
		retval = -1;
	}

	return (retval);
}

ebex_cfg_err_t ebex_cfg_geterrno(void)
{
	ebex_cfg_err_t retval;

	retval = ebex_cfg_errno;

	return (retval);
}

void ebex_cfg_seterrstr(const char *usererrmsg)
{
	ebex_cfg_usererrmsg = e_strdup(usererrmsg);
}

char *ebex_cfg_geterrstr(void)
{
	static char retval[512];
	const char *errmsg = NULL;

	switch (ebex_cfg_errno)
	{
		case EBEX_CFG_ERR_NONE:
			errmsg = "Success";
			break;
		case EBEX_CFG_ERR_INVCMD:
			errmsg = "Invalid command or option";
			break;
		case EBEX_CFG_ERR_INVSECTION:
			errmsg = "Invalid section";
			break;
		case EBEX_CFG_ERR_INVDATA:
			errmsg = "Invalid application data (internal error)";
			break;
		case EBEX_CFG_ERR_BADFORMAT:
			errmsg = "Bad data specified or incorrect format.";
			break;
		case EBEX_CFG_ERR_CANTOPEN:
			errmsg = "Can't open file.";
			break;
		case EBEX_CFG_ERR_CALLBACK:
			errmsg = "Error return from application handler.";
			break;
		case EBEX_CFG_ERR_ENOMEM:
			errmsg = "Insufficient memory.";
			break;
		default:
			errmsg = "Unknown error";
			break;
	}

	snprintf(retval, sizeof(retval), "%s:%i: %s", ebex_cfg_errfile ? ebex_cfg_errfile : "<no file>", ebex_cfg_errline,
			errmsg);
	retval[sizeof(retval) - 1] = '\0';

	return (retval);
}


static int ebex_cfg_process_file(const char *m_cfgfile, const char *m_prefix)
{
	FILE *configfp = NULL;
	char linebuf[EBEX_CFG_LINEBUF_LEN] = { '\0' };
	char *linebuf_ptr = NULL;
	char *tmp_ptr = NULL;
	char *lastsection = NULL;
	char qualifbuf[EBEX_CFG_LINEBUF_LEN] = { '\0' };
	char *cmd = NULL;
	char *temp_val = NULL;
	char *sep = NULL;
	char *cmdend = NULL;
	char *fgetsret = NULL;

	int var_retval = -1;
	int ebex_cfg_retval = -1;
	int retval = 0;

	bool invalid_section = 0, ignore_section = 0;
	ebex_cfg_err_t save_cfg_errno = EBEX_CFG_ERR_NONE;

	if (m_prefix != NULL)
	{
		/* Copy the prefix, if specified. */
		strncpy(qualifbuf, m_prefix, sizeof(qualifbuf) - 1);
	}

	ebex_cfg_errfile = m_cfgfile;
	ebex_cfg_errline = 0;

	if (m_cfgfile == NULL)
	{
		ebex_cfg_errno = EBEX_CFG_ERR_INVDATA;
		return (-1);
	}

	if (!(configfp = fopen(m_cfgfile, "r")))
	{
		ebex_cfg_errno = EBEX_CFG_ERR_CANTOPEN;
		return (-1);
	}

	while (1)
	{
		fgetsret = fgets(linebuf, sizeof(linebuf) - 1, configfp);
		if (fgetsret == NULL)
		{
			break;
		}
		if (feof(configfp))
		{
			break;
		}

		ebex_cfg_errline++;

		/* Remove trailing crap (but not spaces). */
		linebuf_ptr = &linebuf[strlen(linebuf) - 1];
		while (*linebuf_ptr < ' ' && linebuf_ptr >= linebuf)
		{
			*linebuf_ptr = '\0';
			linebuf_ptr--;
		}

		/* Remove leading spaces. */
		linebuf_ptr = &linebuf[0];
		while ((*linebuf_ptr == ' ' || *linebuf_ptr == '\t') && linebuf_ptr < (linebuf + sizeof(linebuf)))
		{
			linebuf_ptr++;
		}

		/* Handle section header. */
		if (linebuf_ptr[0] == '<' && linebuf_ptr[strlen(linebuf_ptr) - 1] == '>'
				&& linebuf_ptr < (linebuf + sizeof(linebuf)))
		{
			/* Remove < and > from around the data. */
			linebuf_ptr[strlen(linebuf_ptr) - 1] = '\0';
			linebuf_ptr++;

			/* Lowercase the command part of the section. */
			tmp_ptr = linebuf_ptr;
			while (*tmp_ptr != '\0' && *tmp_ptr != ' ' && tmp_ptr < (linebuf + sizeof(linebuf)))
			{
				*tmp_ptr = (char)tolower(*tmp_ptr);
				tmp_ptr++;
			}

			/* If this is a close section command, handle it */
			if (linebuf_ptr[0] == '/')
			{
				linebuf_ptr++;
				cmd = linebuf_ptr;

				/* Find the last section closed. */
				tmp_ptr = strrchr(qualifbuf, '.');
				if (tmp_ptr == NULL)
				{
					lastsection = qualifbuf;
					tmp_ptr = qualifbuf;
				}
				else
				{
					lastsection = tmp_ptr + 1;
				}

				if (strcmp(cmd, lastsection) != 0)
				{
					fprintf(stderr, "Section closing does not match last opened section.\n");
					fprintf(stderr, "Last opened = \"%s\", Closing = \"%s\"\n", lastsection, cmd);
					retval = -1;
					ebex_cfg_errno = EBEX_CFG_ERR_BADFORMAT;

					/* For this error, we abort immediately. */
					break;
				}

				var_retval = ebex_cfg_process_var(qualifbuf, NULL, NULL, EBEX_CFG_FLAGS_SECTIONEND);
				if (var_retval < 0)
				{
					fprintf(stderr, "Invalid section terminating: \"%s\"\n", qualifbuf);
				}

				/* Remove the "lastsection" part.. */
				*tmp_ptr = '\0';

				/* We just successfully closed the last section opened,
				 we must be in a valid section now since we only open
				 sections from within valid sections. */
				invalid_section = 0;
				ignore_section = 0;

				continue;
			}
			/* Otherwise, open a new section. */

			/* Don't open a section from an invalid section. */
			if (invalid_section == 1 || ignore_section == 1)
			{
				continue;
			}

			/* Parse out any argument passed. */
			sep = strpbrk(linebuf_ptr, " \t");

			if (sep != NULL)
			{
				cmdend = sep;
				/* Delete space at the end of the command. */
				cmdend--; /* It currently derefs to the seperator.. */
				while (*cmdend <= ' ')
				{
					*cmdend = '\0';
					cmdend--;
				}

				/* Delete the seperator char and any leading space. */
				*sep = '\0';
				sep++;
				while (*sep == ' ' || *sep == '\t')
				{
					sep++;
				}
				temp_val = sep;
			}
			else
			{
				temp_val = "";
			}

			cmd = linebuf_ptr;

			if (qualifbuf[0] != '\0')
			{
				strncat(qualifbuf, ".", sizeof(qualifbuf) - strlen(qualifbuf) - 1);
			}
			strncat(qualifbuf, cmd, sizeof(qualifbuf) - strlen(qualifbuf) - 1);

			var_retval = ebex_cfg_process_var(qualifbuf, temp_val, NULL, EBEX_CFG_FLAGS_SECTIONSTART);
			if (var_retval < 0)
			{
				fprintf(stderr, "Invalid section: \"%s\"\n", qualifbuf);
				invalid_section = 1;
				ebex_cfg_errno = EBEX_CFG_ERR_INVSECTION;
				retval = -1;
			}
			if (var_retval)
			{
				ignore_section = 1;
			}
			continue;
		}

		/* Drop comments and blank lines. */
		if (*linebuf_ptr == '#' || (linebuf_ptr[0] == '/' && linebuf_ptr[1] == '/' ) || *linebuf_ptr == '\0')
		{
			continue;
		}

		/* Don't handle things for a section that doesn't exist. */
		if( invalid_section || ignore_section )
		{
			continue;
		}

		/* Find the command and the data in the line. */
		sep = strpbrk(linebuf_ptr, " \t=");
		if (sep != NULL)
		{
			cmdend = sep;

			/* Delete space at the end of the command. */
			cmdend--; /* It currently derefs to the seperator.. */
			while (*cmdend <= ' ' && cmdend >= sep)
			{
				*cmdend = '\0';
				cmdend--;
			}

			/* Delete the separator character, an optional equality sign and any leading space. */
			*sep = '\0';
			sep++;
			while (*sep == ' ' || *sep == '\t' || *sep == '=')
			{
				sep++;
			}
			temp_val = sep;
		}
		else
		{
			temp_val = NULL;
		}

		cmd = linebuf_ptr;

		/* Handle special commands. */
		if (strcasecmp(cmd, "include") == 0)
		{
			if (temp_val == NULL)
			{
				ebex_cfg_errno = EBEX_CFG_ERR_BADFORMAT;
				retval = -1;
				fprintf(stderr, "Invalid include command.\n");
				continue;
			}

			ebex_cfg_retval = ebex_cfg_process_include(temp_val, qualifbuf);
			if (ebex_cfg_retval < 0)
			{
				fprintf(stderr, "Error in included file.\n");
				retval = -1;
			}
			continue;
		}

		/* Create the fully qualified variable name. */
		if (qualifbuf[0] != '\0')
		{
			strncat(qualifbuf, ".", sizeof(qualifbuf) - strlen(qualifbuf) - 1);
		}
		strncat(qualifbuf, cmd, sizeof(qualifbuf) - strlen(qualifbuf) - 1);

		/* Call the parent and tell them we have data. */
		save_cfg_errno = ebex_cfg_errno;
		ebex_cfg_errno = EBEX_CFG_ERR_NONE;
		var_retval = ebex_cfg_process_var(qualifbuf, NULL, temp_val, EBEX_CFG_FLAGS_VAR);
		if (var_retval < 0)
		{
			if (ebex_cfg_errno == EBEX_CFG_ERR_NONE)
			{
				dief("Invalid command: '%s'", qualifbuf);
				ebex_cfg_errno = EBEX_CFG_ERR_INVCMD;
			}
			else
			{
				fprintf(stderr, "Error processing command %s (command was valid, but an error occured, errno was set)\n", qualifbuf);
			}
			retval = -1;
			break;
		}
		else
		{
			ebex_cfg_errno = save_cfg_errno;
		}

		/* Remove the "cmd" part of the buffer. */
		tmp_ptr = strrchr(qualifbuf, '.');
		if (tmp_ptr == NULL)
		{
			tmp_ptr = qualifbuf;
		}
		*tmp_ptr = '\0';
	}

	fclose(configfp);

	return (retval);
}
